#include "SmokeBoxWin.h"
#include <assert.h>

CSmokeBoxWin::CSmokeBoxWin(int w, int h):glutWindow(w,h)
{
	m_boundLeft= m_boundBottom = -3.f;
	m_boundRight = m_boundTop = 3.f;
	m_boundNear = 0.1f;
	m_boundFar = 1000.f;

	m_gridN = 32;
	m_hc = 0.08f;		m_hx = m_hy = m_hz = m_hc * m_gridN;	m_offset = 1.f / (2 * (m_gridN + 1));
	m_timeStep = 0.1f;
	m_fluidDiff = 0.f;
	m_fluidVisc = 0.f;
	m_fluidSource = 300.f;
	m_pFluidSolver = new CFluidSolver(m_gridN, m_timeStep, m_fluidDiff, m_fluidVisc);

	//camera
	m_eye[0] = 0; m_eye[1] = 0.f; m_eye[2] = 15.f;
	m_rot[0] = m_rot[1] = m_rot[2] = 0.f;
	m_button = -1;

	m_isUpdated = false;

	OnInit();
}

CSmokeBoxWin::~CSmokeBoxWin(void)
{
	if (m_pFluidSolver != NULL)
		delete m_pFluidSolver;
}

void CSmokeBoxWin::UpdateIntersTex()
{
	glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW); glPushMatrix();
	glLoadIdentity();
	glTranslatef(-m_eye[0], -m_eye[1], -m_eye[2]);
	glRotatef(m_rot[0], 1.f, 0.f, 0.f);
	glRotatef(m_rot[1], 0.f, 1.f, 0.f);
	glRotatef(m_rot[2], 0.f, 0.f, 1.f);


	cgGLSetStateMatrixParameter(m_cgModelViewProjParam, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
	cgGLSetStateMatrixParameter(m_cgModelViewParam, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_IDENTITY);
	cgGLSetStateMatrixParameter(m_cgModelViewInvParam, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE);
	cgGLSetParameter1f(m_cgCellDim, m_hc);
	cgGLSetParameter3f(m_cgGridDim, m_hx, m_hy, m_hz);

	//run 1st pass
	CGpass pass = cgGetNamedPass(m_cgTechnique, "p0");
	cgSetPassState(pass);
	drawBoundingBox(m_hx, m_hy, m_hz, m_offset);
	cgResetPassState(pass);

	//run 2st pass
	pass = cgGetNamedPass(m_cgTechnique, "p1");
	cgSetPassState(pass);
	drawBoundingBox(m_hx, m_hy, m_hz, m_offset);
	cgResetPassState(pass);

	glPopMatrix();
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	cgGLSetTextureParameter(m_cgIntersTexParam, m_fbo_texture);
	cgGLSetTextureParameter(m_cgSmokeTexParam, m_smoketexture);
}

void CSmokeBoxWin::drawBoundingBox(float hx, float hy, float hz, float offset)
{
	//x = 0;
	glBegin(GL_QUADS);
	glTexCoord3f(offset, offset, offset); glVertex3f(0.0f, 0.0f, 0.0f); // The bottom left corner
	glTexCoord3f(offset, offset, 1-offset); glVertex3f(0.0f, 0.0f, hz); // The bottom right corner
	glTexCoord3f(offset, 1-offset, 1-offset); glVertex3f(0.0f, hy, hz); // The top right corner
	glTexCoord3f(offset, 1-offset, offset); glVertex3f(0.0f, hy, 0.0f); // The top left corner
	glEnd();
	// y = 0;
	glBegin(GL_QUADS);
	glTexCoord3f(offset, offset, offset); glVertex3f(0.0f, 0.0f, 0.0f); // The bottom left corner
	glTexCoord3f(1-offset, offset, offset); glVertex3f(hx, 0.0f, 0.0f); // The bottom right corner
	glTexCoord3f(1-offset, offset, 1-offset); glVertex3f(hx, 0.0f, hz); // The top right corner
	glTexCoord3f(offset, offset, 1-offset); glVertex3f(0.0f, 0.0f, hz); // The top left corner
	glEnd();
	// z = 0;
	glBegin(GL_QUADS);
	glTexCoord3f(offset, offset, offset); glVertex3f(0.0f, 0.0f, 0.0f); // The bottom left corner
	glTexCoord3f(offset, 1-offset, offset); glVertex3f(0.0f, hy, 0.0f); // The bottom right corner
	glTexCoord3f(1-offset, 1-offset, offset); glVertex3f(hx, hy, 0.0f); // The top right corner
	glTexCoord3f(1-offset, offset, offset); glVertex3f(hx, 0.0f, 0.0f); // The top left corner
	glEnd();
	// x = w;
	glBegin(GL_QUADS);
	glTexCoord3f(1-offset, 1-offset, 1-offset); glVertex3f(hx, hy, hz); // The bottom left corner
	glTexCoord3f(1-offset, offset, 1-offset); glVertex3f(hx, 0.0f, hz); // The bottom right corner
	glTexCoord3f(1-offset, offset, offset); glVertex3f(hx, 0.0f, 0.0f); // The top right corner
	glTexCoord3f(1-offset, 1-offset, offset); glVertex3f(hx, hy, 0.0f); // The top left corner
	glEnd();
	// y = h;
	glBegin(GL_QUADS);
	glTexCoord3f(offset, 1-offset, offset); glVertex3f(0.0f, hy, 0.0f); // The bottom left corner
	glTexCoord3f(offset, 1-offset, 1-offset); glVertex3f(0.0f, hy, hz); // The bottom right corner
	glTexCoord3f(1-offset, 1-offset, 1-offset); glVertex3f(hx, hy, hz); // The top right corner
	glTexCoord3f(1-offset, 1-offset, offset); glVertex3f(hx, hy, 0.0f); // The top left corner
	glEnd();
	// z = l;
	glBegin(GL_QUADS);
	glTexCoord3f(offset, 1-offset, 1-offset); glVertex3f(0.0f, hy, hz); // The bottom left corner
	glTexCoord3f(offset, offset, 1-offset); glVertex3f(0.0f, 0.0f, hz); // The bottom right corner
	glTexCoord3f(1-offset, offset, 1-offset); glVertex3f(hx, 0.0f, hz); // The top right corner
	glTexCoord3f(1-offset, 1-offset, 1-offset); glVertex3f(hx, hy, hz); // The top left corner
	glEnd();
}


void CSmokeBoxWin::OnRender()
{
	if (!m_isUpdated)
	{
		UpdateIntersTex();
		m_isUpdated = true;
	}

	CGpass pass = cgGetNamedPass(m_cgTechnique, "raycastpass");
	cgSetPassState(pass);

	//render fullscreen quad
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION); glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0.f, 1.f, 0.f, 1.f);
	glMatrixMode(GL_MODELVIEW); glPushMatrix();
	glLoadIdentity();
	glBegin(GL_QUADS);
	glTexCoord2f(0.f, 0.f); glVertex2f(0.f, 0.f);
	glTexCoord2f(1.f, 0.f); glVertex2f(1.f, 0.f);
	glTexCoord2f(1.f, 1.f); glVertex2f(1.f, 1.f);
	glTexCoord2f(0.f, 1.f); glVertex2f(0.f, 1.f);
	glEnd();

	cgResetPassState(pass);

	glPopMatrix();
	glMatrixMode(GL_PROJECTION); glPopMatrix();

	glutSwapBuffers();

	//////////////////////////////////////////////////////////////////////////

	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//glMatrixMode(GL_MODELVIEW); glPushMatrix();
	//glLoadIdentity();
	//glTranslatef(-m_eye[0], -m_eye[1], -m_eye[2]);
	//glRotatef(m_rot[0], 1.f, 0.f, 0.f);
	//glRotatef(m_rot[1], 0.f, 1.f, 0.f);
	//glRotatef(m_rot[2], 0.f, 0.f, 1.f);	

	////get texture data
	//float* texdata = new float[m_winWidth * m_winHeight * 4];

	//glBindTexture(GL_TEXTURE_2D, m_fbo_texture);
	//glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, texdata);
	//glBindTexture(GL_TEXTURE_2D, 0);

	//glBegin(GL_POINTS);
	//glColor3f(1, 0, 0);
	//for (int i = 0; i < m_winWidth; i++)
	//{
	//	for (int j = 0; j < m_winHeight; j++)
	//	{
	//		int id = j * m_winWidth + i;
	//		float x		= texdata[4 * id];
	//		float y		= texdata[4 * id + 1];
	//		float z		= texdata[4 * id + 2];
	//		float ns	= texdata[4 * id + 3];

	//		if (ns != 0)
	//		{
	//			glVertex3f(x, y, z);
	//		}
	//	}
	//}
	//glEnd();
	//glPopMatrix();

	//glutSwapBuffers();
	//delete[] texdata;
	
	//////////////////////////////////////////////////////////////////////////
	//glClearColor(0.f, 0.f, 0.f, 0.f);
	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//glMatrixMode(GL_MODELVIEW); glPushMatrix();
	//glLoadIdentity();
	//glTranslatef(-m_eye[0], -m_eye[1], -m_eye[2]);
	//glRotatef(m_rot[0], 1.f, 0.f, 0.f);
	//glRotatef(m_rot[1], 0.f, 1.f, 0.f);
	//glRotatef(m_rot[2], 0.f, 0.f, 1.f);

	//glBegin(GL_POINTS);
	//int m_N = m_gridN;
	//int i,j,k;
	//float x,y,z;
	//for (i = 0; i <= m_N; i++){
	//	x = (i - 0.5f) * m_hc;
	//	for (j = 0; j <= m_N; j++){
	//		y = (j - 0.5f) * m_hc;
	//		for (k = 0; k <= m_N; k++){
	//			z = (k - 0.5f) * m_hc;
	//			glColor3f(m_pFluidSolver->m_dens[IX3(i,j,k)], 0.f, 0.f);
	//			glVertex3f(x, y, z);
	//		}
	//	}
	//}
	//glEnd();

	//glPopMatrix();
	//glutSwapBuffers();
}

void CSmokeBoxWin::OnIdle()
{
	//add smoke source each time step
	int mid = (m_gridN + 1) / 2;
	m_pFluidSolver->SetSource(mid, 1, mid, m_fluidSource * 0.05f);

	m_pFluidSolver->VelocitySolver();
	m_pFluidSolver->DensitySolver();

	//bind smoke data to 3d texture
	if (m_smoketexture != 0) glDeleteTextures(1, &m_smoketexture);
	glGenTextures(1, &m_smoketexture);
	glBindTexture(GL_TEXTURE_3D, m_smoketexture);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
	glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA16, m_gridN + 2, m_gridN + 2, m_gridN + 2, 0, 
		GL_ALPHA, GL_FLOAT, m_pFluidSolver->m_dens);
	glBindTexture(GL_TEXTURE_3D, 0);


	glutPostRedisplay();
}

void CSmokeBoxWin::InitFrameBuffer()
{
	if (m_fbo_texture != 0) glDeleteTextures(1, &m_fbo_texture);
	glGenTextures(1, &m_fbo_texture);	
	/*color texture*/
	glBindTexture(GL_TEXTURE_2D, m_fbo_texture);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, m_winWidth, m_winHeight, 0, GL_RGBA, GL_FLOAT, NULL);
	glBindTexture(GL_TEXTURE_2D, 0);

	/*create a render buffer object to store depth info*/
	if (m_rbo != 0) glDeleteRenderbuffers(1, &m_rbo);
	glGenRenderbuffers(1, &m_rbo);
	glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, m_winWidth, m_winHeight);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);

	/*frame buffer*/
	if (m_fbo != 0) glDeleteFramebuffers(1, &m_fbo);
	glGenFramebuffers(1, &m_fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_fbo_texture, 0);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
		GL_RENDERBUFFER, m_rbo);

	/*check state*/
	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (status != GL_FRAMEBUFFER_COMPLETE)
	{
		fprintf(stderr, "Couldn't create frame buffer. Error: %x\n", status);
		exit(1);
	}
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void CSmokeBoxWin::CheckforCgError(const char* situation)
{
	CGerror error;
	const char *string = cgGetLastErrorString(&error);

	if (error != CG_NO_ERROR) {
		if (error == CG_COMPILER_ERROR) {
			fprintf(stderr,
				"Program: \n"
				"Situation: %s\n"
				"Error: %s\n\n"
				"Cg compiler output...\n%s",
				m_programName, situation, string,
				cgGetLastListing(m_cgContext));
		} else {
			fprintf(stderr,
				"Program: \n"
				"Situation: %s\n"
				"Error: %s",
				m_programName, situation, string);
		}
		exit(1);
	}
}

void CSmokeBoxWin::UseSamplerParameter(CGeffect effect, const char *paramName, GLuint texobj)
{
	CGparameter param = cgGetNamedEffectParameter(effect, paramName);

	if (!param) {
		fprintf(stderr, "%s: expected effect parameter named %s\n", m_programName, paramName);
		exit(1);
	}
	cgGLSetTextureParameter(param, texobj);
	cgSetSamplerState(param);
}

void CSmokeBoxWin::InitCgFX(void)
{
	sprintf_s(m_programName, "%s", "volume intersection");

	m_cgContext = cgCreateContext();
	CheckforCgError("creating context");
	cgGLRegisterStates(m_cgContext);
	CheckforCgError("registering standard CgFX states");
	cgGLSetManageTextureParameters(m_cgContext, CG_TRUE);
	CheckforCgError("manage texture parameters");

	m_cgEffect = cgCreateEffectFromFile(m_cgContext, "cg_shader/empty1.cgfx", NULL);
	CheckforCgError("creating .cgfx effect");
	assert(m_cgEffect);

	m_cgTechnique = cgGetFirstTechnique(m_cgEffect);
	while (m_cgTechnique && cgValidateTechnique(m_cgTechnique) == CG_FALSE)
	{
		fprintf(stderr, "%s: Technique %s did not validate. Skipping.\n", 
			m_programName, cgGetTechniqueName(m_cgTechnique));
		m_cgTechnique = cgGetNextTechnique(m_cgTechnique);
	}
	if (m_cgTechnique)
	{
		fprintf(stderr, "%s: Use technique %s.\n",
			m_programName, cgGetTechniqueName(m_cgTechnique));
	} else
	{
		fprintf(stderr, "%s: No valid technique\n", m_programName);
		exit(1);
	}

	m_cgModelViewProjParam = cgGetEffectParameterBySemantic(m_cgEffect, "WorldViewProjection");
	if (!m_cgModelViewProjParam)
	{
		fprintf(stderr, "%s: must find parameter with WorldViewProjection semantic\n", m_programName);
		exit(1);
	}

	m_cgModelViewParam = cgGetEffectParameterBySemantic(m_cgEffect, "WorldView");
	if (!m_cgModelViewParam)
	{
		fprintf(stderr, "%s: must find parameter with WorldView semantic\n", m_programName);
		exit(1);
	}

	m_cgModelViewInvParam = cgGetEffectParameterBySemantic(m_cgEffect, "WorldViewInverse");
	if (!m_cgModelViewInvParam)
	{
		fprintf(stderr, "%s: must find parameter with WorldViewInverse semantic\n", m_programName);
		exit(1);
	}

	m_cgIntersTexParam = cgGetNamedEffectParameter(m_cgEffect, "ColrSampler");
	if (!m_cgIntersTexParam)
	{
		fprintf(stderr, "%s: must find parameter with name ColrSampler\n", m_programName);
		exit(1);
	}

	m_cgSmokeTexParam = cgGetNamedEffectParameter(m_cgEffect, "smokeData");
	if (!m_cgSmokeTexParam)
	{
		fprintf(stderr, "%s: must find parameter with name smokeData\n", m_programName);
		exit(1);
	}

	m_cgCellDim = cgGetNamedEffectParameter(m_cgEffect, "cellDim");
	if (!m_cgCellDim)
	{
		fprintf(stderr, "%s: must find parameter with name cellDim\n", m_programName);
		exit(1);
	}

	m_cgGridDim = cgGetNamedEffectParameter(m_cgEffect, "gridDim");
	if (!m_cgGridDim)
	{
		fprintf(stderr, "%s: must find parameter with name gridDim\n", m_programName);
		exit(1);
	}
}

void CSmokeBoxWin::OnInit()
{
	//retrieve sys info by m_glInfo...
	m_glInfo.Init();

	//init Cg shader
	InitCgFX();

	//ids for framebuffer
	m_fbo_texture = 0;
	m_rbo = 0;
	m_fbo = 0;
	m_smoketexture = 0;
}

void CSmokeBoxWin::OnPreDisplay()
{
	glClearColor(0.1f, 0.3f, 0.6f, 0.f);
	glClearDepth(1.f);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_DEPTH_TEST);
}

void CSmokeBoxWin::OnResize(int nWidth, int nHeight)
{
	m_winWidth = nWidth;
	m_winHeight = nHeight;

	float aspect_ratio = (float) m_winWidth /m_winHeight;
	float w = (m_boundTop - m_boundBottom) * aspect_ratio;
	float wc = (m_boundRight + m_boundLeft) / 2.f;
	m_boundLeft = wc - w / 2.f;
	m_boundRight = wc + w/ 2.f;

	glViewport(0, 0, m_winWidth, m_winHeight);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(m_boundLeft, m_boundRight, m_boundBottom, m_boundTop, m_boundNear, m_boundFar);
	//gluPerspective(50.f, aspect_ratio, m_boundNear, m_boundFar);

	//initialize again framebuffer
	InitFrameBuffer();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void CSmokeBoxWin::OnClose()
{

}

enum {
	BUTTON_LEFT
};

void CSmokeBoxWin::OnMouseDown(int button, int x, int y)
{
	m_oldX = x;
	m_oldY = y;
	switch (button)
	{
	case GLUT_LEFT_BUTTON:
		m_button = BUTTON_LEFT;
		break;
	}
}

void CSmokeBoxWin::OnMouseUp(int button, int x, int y)
{
	m_button = -1;
}

inline void clamp(float *v)
{
	int i;

	for (i = 0; i < 3; i ++)
		if (v[i] > 360 || v[i] < -360)
			v[i] = 0;
}

void CSmokeBoxWin::OnMouseMove(int x, int y, int mod)
{
    if (m_button == BUTTON_LEFT)
    {
        m_rot[0] -= (m_oldY - y);
        m_rot[1] -= (m_oldX - x);
		clamp(m_rot);
		m_isUpdated = false;
		glutPostRedisplay();
    }
    m_oldX = x;
    m_oldY = y;
}

void CSmokeBoxWin::OnKeyDown(int nKey, char cAscii)
{
	switch (cAscii)
	{
	case 'c':
	case 'C':
		m_pFluidSolver->ClearData();
		break;
	case 's':
	case'S':
		//add smoke source each time step
		int mid = (m_gridN + 1) / 2;
		m_pFluidSolver->SetSource(mid, 1, mid, m_fluidSource * 0.05f);
		break;
	}
}

void CSmokeBoxWin::OnKeyUp(int nKey, char cAscii)
{

}